/*
 * Object   : CClient
 * Type     : Backend
 *
 * Narrator : Kamran H. Khan 
 * Email    : KAMRAN.KHAN@MaximaLabssolutions.com, KAMRANHK@gmail.com
 * 
 * Copyright :
 *            MaximaLabs Solutions (R) 2009
 * 
 * 
 * Description: 
 * Provides tcp client related functionalities
 * 
 * Revision History:
 * 
 * Date(MMM/dd/yy)      Owner           Description
 * ----------------------------------------------------------------------------------------------------------------------------------------------
 * May/30/09            Kamran H. Khan  Added skeleton code
 * May/30/09            Kamran H. Khan  Added more methods
 * May/30/09            Kamran H. Khan  Added logging methods for SoapExceptions, XmlExceptions, generic Exception 
 * 
 * 
 * 
*/


using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;

using System.ComponentModel;
using System.Threading;
using Maxima.Common;

namespace Maxima.Communicator
{
    public class CClient : CNetMgr, IDisposable
    {
        private TcpClient m_theClient;
        private NetworkStream m_nwkStream;
        private ASCIIEncoding theEncoder = new ASCIIEncoding();
        private string m_strIP;
        private Thread m_thdConnector;
        private Thread m_thdClientHandler;
        private string m_strStatus = string.Empty;


        private const int WAIT_FOR_SOME_SECONDS = 3;

        public delegate void StatusChangeEventHandler(string strData);
        public event StatusChangeEventHandler OnStatusChange;

        private bool m_bIsConnected = false;
        public bool IsConnected
        {
            get
            {
                if (m_theClient != null)
                {
                    return m_bIsConnected = m_theClient.Connected;
                }
                else
                    return m_bIsConnected = m_bIsConnected;

            }
        }

        [Category("Settings")]
        [Description("IP Address")]
        [BrowsableAttribute(true)]
        public string IP
        {
            get { return m_strIP; }
            set { m_strIP = value; }
        }
        private int m_nPort;
        [Category("Settings")]
        [Description("Port")]
        [BrowsableAttribute(true)]
        public int Port
        {
            get { return m_nPort; }
            set { m_nPort = value; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CClient"/> class.
        /// </summary>
        /// <param name="strIP">The STR IP.</param>
        /// <param name="nPort">The n port.</param>
        public CClient(string strIP, int nPort)
            : base(strIP, nPort, false)
        {
            m_nPort = nPort;
            m_strIP = strIP;
        }

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public void Start()
        {
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Inside");
            // use threadings.
            m_thdConnector = new System.Threading.Thread(delegate() { Connector(); });
            m_thdConnector.IsBackground = true;
            m_thdConnector.Start();
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Thread started.");
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "End");

        }



        /// <summary>
        /// Connectors this instance.
        /// </summary>
        private void Connector()
        {
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Inside");
            m_theClient = new TcpClient();

            IPEndPoint epServerID = new IPEndPoint(IPAddress.Parse(m_strIP), Convert.ToInt32(m_nPort));

            //Continue, unless it gets connected.
            while (!m_theClient.Connected)
            {
                try
                {
                    Log("Trying to connect... to: " + m_strIP + ":" + m_nPort.ToString());
                    CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Trying to connect... to: " + m_strIP + ":" + m_nPort.ToString(), System.Diagnostics.EventLogEntryType.Information);
                    m_theClient.Connect(epServerID);
                    m_bIsConnected = true;
                    break;//here, this means it got the connection successfully; lets do the rest.
                }
                catch (Exception exc)
                {
                    Log("Couldnt find server.");
                    CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Couldnt find server. " + exc.Message, System.Diagnostics.EventLogEntryType.FailureAudit);
                }

                Wait(); //

            }

            Log("Connected. [" + m_strIP + ":" + m_nPort.ToString() + "]");
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Connected. [" + m_strIP + ":" + m_nPort.ToString() + "]", System.Diagnostics.EventLogEntryType.Information);


            m_nwkStream = m_theClient.GetStream();

            m_thdClientHandler = new System.Threading.Thread(delegate() { HandleClientEx(); });
            m_thdClientHandler.IsBackground = true;

            m_thdClientHandler.Start();
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "End");
        }

        /// <summary>
        /// Waits this instance.
        /// </summary>
        private void Wait()
        {
            Log("Will try in secs: " + WAIT_FOR_SOME_SECONDS.ToString());
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Will try again(in secs): " + WAIT_FOR_SOME_SECONDS.ToString(), System.Diagnostics.EventLogEntryType.Information);

            System.Threading.Thread.Sleep(WAIT_FOR_SOME_SECONDS * 1000);
        }

        /// <summary>
        /// Handles the client.
        /// </summary>
        private void HandleClient()
        {
            //CHelper.Log("CClient::HandleClient()");
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Inside method.", System.Diagnostics.EventLogEntryType.Information);
            ASCIIEncoding theEncoding = new ASCIIEncoding();
            byte[] bytMessage = new byte[READ_BUFFER_MAX_SIZE];

            try
            {
                while (true)
                {
                    int nBytesRead = 0;

                    if (m_theClient == null || m_nwkStream == null)
                        break;

                    if (!m_theClient.Connected)
                        break;

                    //CHelper.Log("Waiting for data");
                    CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Waiting...", System.Diagnostics.EventLogEntryType.Information);

                    if (m_nwkStream.CanRead && m_nwkStream != null)
                        nBytesRead = m_nwkStream.Read(bytMessage, 0, bytMessage.Length);

                    if (nBytesRead == 0)
                        continue;

                    string strMsg = theEncoding.GetString(bytMessage);


                    //Log("\r\nRX: MSG[" + strMsg);
                    //Log("MSG[" + strMsg + "]");
                    Log(strMsg);
                    CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "RX: " + strMsg, System.Diagnostics.EventLogEntryType.Information);

                    //Log("\r\nRX: MSG[" + m_strIP + ":" + m_nPort + "]" + strMsg);
                    //Log("MSG[" + m_strIP + ":" + m_nPort + "]" + strMsg);


                    if (strMsg != null && strMsg == "logout")
                        break;
                }
            }
            catch (Exception ex)
            {
                Error(ex.Message);
                CLogger.GetInstance().Error(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Couldnt handle client", ex);
            }
        }

        private void HandleClientEx()
        {
            //CHelper.Log("CClient::HandleClient()");
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Inside method.", System.Diagnostics.EventLogEntryType.Information);
            //ASCIIEncoding theEncoding = new ASCIIEncoding();
            //byte[] bytMessage = new byte[READ_BUFFER_MAX_SIZE];
            bool bDisconnected = false;

            try
            {
                while (true)
                {
                    int nBytesRead = 0;

                    if (m_theClient == null || m_nwkStream == null)
                        break;

                    if (!m_theClient.Connected)
                        break;

                    //CHelper.Log("Waiting for data");
                    CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Waiting...", System.Diagnostics.EventLogEntryType.Information);

                    string strHeaderBytes = ReadData(m_theClient.Client, //the client
                                                        4, // bytes to be read
                                                        out bDisconnected);//should disconnect?
                    string strMsg = string.Empty;
                    int nMsgLength = 0;
                    bool bReadable = int.TryParse(strHeaderBytes.ToString(), out nMsgLength);
                    if (!bReadable || nMsgLength < 0)
                    {
                        CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Invalid header bytes : [" + strHeaderBytes + "]");
                        continue;//lets wait for next bytes.
                    }
                    strMsg = ReadData(m_theClient.Client, nMsgLength, out bDisconnected);
                    Log(strMsg);
                    CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "RX: " + strMsg, System.Diagnostics.EventLogEntryType.Information);

                    if (strMsg != null && strMsg == "0000")
                    {
                        CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Disconnect signal acknowledged by the server, lets close.", System.Diagnostics.EventLogEntryType.Information);
                        Stop();
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                Error(ex.Message);
                CLogger.GetInstance().Error(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Couldnt handle client", ex);
            }
        }

        string ReadData(Socket sckClient, int nBytesToRead, out bool bShouldDisconnect)
        {
            bShouldDisconnect = false;

            byte[] byteBuffer = new byte[nBytesToRead];
            Array.Clear(byteBuffer, 0, byteBuffer.Length);

            int nDataRead = 0;
            int nStartIndex = 0;
            int nBytesRead = 0;

            //while (nDataRead <= nBytesToRead)
            while (nDataRead < nBytesToRead)
            {
                try
                {
                    nBytesRead = sckClient.Receive(byteBuffer, nStartIndex, nBytesToRead - nStartIndex, SocketFlags.None);
                }
                catch (SocketException ex)
                {
                    
                    Error(ex.Message);
                    CLogger.GetInstance().Error(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Couldnt handle client", ex);
                }
                if (0 == nBytesRead)
                {
                    bShouldDisconnect = true;
                    CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "0 bytes received; assuming disconnect signal");
                    break;
                }

                nDataRead += nBytesRead;
                nStartIndex += nBytesRead;
                CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Data : [" + Encoding.Default.GetString(byteBuffer, 0, nDataRead) + "]");
                CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Bytes read: " + nBytesRead.ToString());
                CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Bytes remaining: " + (nBytesToRead - nDataRead).ToString());



            }

            return Encoding.Default.GetString(byteBuffer, 0, nDataRead);
        }


        /// <summary>
        /// Sends the specified STR data.
        /// </summary>
        /// <param name="strData">The STR data.</param>
        /// <returns></returns>
        public int Send(string strData)
        {
            //CHelper.Log("CClient::Send()");
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Inside method", System.Diagnostics.EventLogEntryType.Information);

            int nSentBytes = 0;
            if (m_nwkStream == null)
            {
                Log("Nowhere to send.");
                CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Nowhere to send", System.Diagnostics.EventLogEntryType.Information);

                return 0;//means, this client was not connected to any server, lets not send anything.
            }
            else
            {
                byte[] bytBuffer = theEncoder.GetBytes(strData);
                try
                {
                    m_nwkStream.Write(bytBuffer, 0, bytBuffer.Length);
                    m_nwkStream.Flush();
                    bytBuffer = null;
                    nSentBytes = strData.Length;
                }
                catch (System.Exception exc)
                {
                    Error(exc.Message);
                }

                Log("TX: " + strData);//Msg sent, bytes[" + nSentBytes.ToString() + "]");
                CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "TX: " + strData, System.Diagnostics.EventLogEntryType.Information);

            }
            return nSentBytes;
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public void Stop()
        {

            if (m_thdConnector != null)// m_thdClientHandler != null)
            {
                m_bIsConnected = false;
                m_thdConnector.Abort();// m_thdClientHandler.Abort();
                Log("Stopped.");
                CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Stopped", System.Diagnostics.EventLogEntryType.Information);


            }
            else
            {
                Log("Stopped, no thread was found.");
                CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Stopped, no thread was found!", System.Diagnostics.EventLogEntryType.Information);

            }

        }

        /// <summary>
        /// Errors the specified STR MSG.
        /// </summary>
        /// <param name="strMsg">The STR MSG.</param>
        private void Error(string strMsg)
        {
            //CHelper.Log(strMsg);
            //CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Disposing", System.Diagnostics.EventLogEntryType.Information);

            Log("Error: " + strMsg);

            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Error: " + strMsg, System.Diagnostics.EventLogEntryType.Information);

        }

        /// <summary>
        /// Logs the specified STR MSG.
        /// </summary>
        /// <param name="strMsg">The STR MSG.</param>
        private void Log(string strMsg)
        {
            //CHelper.Log(strMsg);
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Log", System.Diagnostics.EventLogEntryType.Information);

            m_strStatus = strMsg;//"\r\nCLI:" +

            if (OnStatusChange != null)
                OnStatusChange(m_strStatus);
        }

        #region IDisposable Members

        /// <summary>
        /// Releases all resources used by the <see cref="T:System.ComponentModel.Component"/>.
        /// </summary>
        public void Dispose()
        {
            //CHelper.Log("Disposing");
            CLogger.GetInstance().Debug(this.GetType().Name, System.Reflection.Emit.MethodBuilder.GetCurrentMethod().Name, "Disposing", System.Diagnostics.EventLogEntryType.Information);
            m_nwkStream.Dispose();

        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Releases all resources used by the <see cref="T:System.ComponentModel.Component"/>.
        /// </summary>
        void IDisposable.Dispose()
        {
            Dispose();
        }

        #endregion


    }
}
